/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.client.sensor.cropcircle;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import nl.cloudfarming.client.logging.AppLogFactory;
import nl.cloudfarming.client.logging.AppLogger;
import nl.cloudfarming.client.model.SensorDataFile;
import nl.cloudfarming.client.model.SensorMetadata;
import nl.cloudfarming.client.sensor.MalformedFileFormatException;
import nl.cloudfarming.client.sensor.SensorEventKey;
import nl.cloudfarming.client.sensor.SensorProcessor;
import nl.cloudfarming.eventbus.BaseGuiEventProducer;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.NbBundle;

/**
 * Cropcircle File processor
 * Processes Cropcircle sensor files 
 *
 * @author Merijn Zengers
 */
public abstract class CropcircleFileProcessor extends SensorProcessor<CropcircleSensorData> {

    // Messages
    private static final String LOGKEY_FAILED_TO_READ_FILE = "cropcircle.fileprocessor.logerror.failed_to_read_file";
    private static final String LOGKEY_PROCESSING_SUMMARY_ERRORMSG = "cropcircle.fileprocessor.logerror.process_summary.nr_of_errors"; 
    private static final String LOGKEY_FAILED_TO_CREATE_DATE_FROM_FILENAME = "cropcircle.fileprocessor.logerror.failed_to_create_date_from_filename";
    private static final String LOGKEY_PROCESSING_START = "cropcircle.fileprocessor.loginfo.process_start";
    private static final String LOGKEY_PROCESSING_SUMMARY_PROCESSEDMSG = "cropcircle.fileprocessor.loginfo.process_summary.nr_of_processed";
     
    // Other variable declarations
    private final ProgressHandle progressHandle;


    //Tested. mem consumtion diference between 10 and 20 thuosand is 3MB. 20 vs 30 is 50 MB
    private static final int BULK_COMMIT_AMOUNT = 1000;
    
    private static final AppLogger LOG = AppLogFactory.getLogger(CropcircleFileProcessor.class);
    private CropcircleServiceImpl cropcircleService = new CropcircleServiceImpl();
    
    private static final String PROPERTY_MONTH = "MM";
    private static final String PROPERTY_YEAR = "YY";
    private static final String PROPERTY_YEAR_INTERNATIONAL = "YYYY";
    private static final String PROPERTY_DAY = "DD";
    protected File fileToProcess;
    private final EventProducer eventProducer = new EventProducer();
        
    public CropcircleFileProcessor(File toProcess) {
        assert toProcess != null;
        this.fileToProcess = toProcess;
        this.progressHandle = ProgressHandleFactory.createHandle(NbBundle.getMessage(this.getClass(), "cropcircle.fileprocessor.loginfo.process_description", toProcess.getName()));
    }

     abstract public CropcircleSensorData parse(String payload, SensorDataFile dataFile) throws CropcircleParseException;
     
     
    @Override
    public void process() {
        SensorDataFile dataFile = null; 
                
        try {
            int success = 0;
            int progress = 0;
            int fail = 0;
            
            LOG.info(LOGKEY_PROCESSING_START, getLogDisplayName(), fileToProcess.getName());
                     
            // Create DataFile
            dataFile = createSensorDataFile(fileToProcess);
            cropcircleService.newSensorDataFile(dataFile);

            List<CropcircleSensorData> persistBuffer = new ArrayList<CropcircleSensorData>();
            
            //First loop through file to determine number of lines. This can be achieved with fileUtils but this will create an out of memory exception with large files.
            BufferedReader br = new BufferedReader(new FileReader(fileToProcess));
            int nrOfLines = 0;
            try {
                while (br.readLine() != null) {
                    nrOfLines++;
                }
            } finally {
                br.close();
            }
            
            try {
                br = new BufferedReader(new FileReader(fileToProcess));
                
                // First line (Header)
                br.readLine();

                this.progressHandle.start(nrOfLines);
                String line;
                while ((line = br.readLine()) != null) {
                    progressHandle.progress(++progress);
                    try {
                        CropcircleSensorData toPersist = parse(line, dataFile);
                        persistBuffer.add(toPersist);
                    } catch (Exception ex) {
                        fail++;
                    }
                    success++;
                    if (success % BULK_COMMIT_AMOUNT == 0) {
                        cropcircleService.bulkPersist(persistBuffer, BULK_COMMIT_AMOUNT);
                        persistBuffer.clear();
                    }

                }

                LOG.info(LOGKEY_PROCESSING_SUMMARY_PROCESSEDMSG, success);
                
                if (fail > 0) {
                    LOG.warning(LOGKEY_PROCESSING_SUMMARY_ERRORMSG, fail);
                }

                //Persist the dataFile. This wil result in persisting the sensordata and metadata as wel.
                if (persistBuffer.size() > 0) {
                    cropcircleService.bulkPersist(persistBuffer, BULK_COMMIT_AMOUNT);
                }
                eventProducer.triggerEvent(SensorEventKey.SENSOR_DATA_IMPORTED);
                
                
                
            } finally {
                progressHandle.finish();
                br.close();
            }
        } catch (ParseException ex) {
            LOG.error(LOGKEY_FAILED_TO_CREATE_DATE_FROM_FILENAME, fileToProcess.getName(), CropcirclePreference.SENSOR_DATA_FILE_FORMAT.getValue());
            LOG.trace(ex);
        } catch (Exception ex) {
            LOG.error(LOGKEY_FAILED_TO_READ_FILE, fileToProcess.getName());
            if (dataFile != null) {
                cropcircleService.removeSensorDataFile(dataFile);
            }
            LOG.trace(ex);
        }
    }
     
    /**
     * Method which  will try to create a date from the filename of a file
     * @param fileName: filename to be created to date
     * @return: the date wich is created from the file name
     * @throws MalformedFileFormatException will be thrown when the property CropcirclePreference.SENSOR_DATA_FILE_FORMAT does not contain:
     * CropcircleFileProcessor.PROPERTY_DAY
     * CropcircleFileProcessor.PROPERTY_MONTH
     * CropcircleFileProcessor.PROPERTY_YEAR
     * @throws ParseException
     */
    @Override
    protected Date getDateFromFileName(File file) throws MalformedFileFormatException, ParseException {
        String props = CropcirclePreference.SENSOR_DATA_FILE_FORMAT.getValue();
        String fileName = file.getName();
        assert !props.isEmpty();

        if (props.contains(PROPERTY_DAY) && props.contains(PROPERTY_MONTH) && props.contains(PROPERTY_YEAR)) {
            int numberOfYearCharacters = 2;
            if (props.contains(PROPERTY_YEAR_INTERNATIONAL)) {
                numberOfYearCharacters = 4;
            }

            String day = fileName.substring(props.indexOf(PROPERTY_DAY), props.indexOf(PROPERTY_DAY) + 2);
            String month = fileName.substring(props.indexOf(PROPERTY_MONTH), props.indexOf(PROPERTY_MONTH) + 2);
            String year = fileName.substring(props.indexOf(PROPERTY_YEAR), props.indexOf(PROPERTY_YEAR) + numberOfYearCharacters);
            StringBuilder dateBuilder = new StringBuilder(day);
            dateBuilder.append(month);
            dateBuilder.append(year);

            SimpleDateFormat date = new SimpleDateFormat("ddMMyy");
            return date.parse(dateBuilder.toString());
        } else {
            StringBuilder sb = new StringBuilder("The property which specifies file format is malformed while creating date from filename: ");
            sb.append(fileName);
            throw new MalformedFileFormatException(sb.toString());
        }
    }

    /**
     * Method which wil create a SensorMetadata object from the prefferences:
     * CropcirclePreference.SENSOR_DATA_FILE_FORMAT
     * CropcirclePreference.CROPCIRCLE_SENSOR_HEIGTH
     * CropcirclePreference.NUMBER_OF_CROPCIRCLE_SENSORS
     * CropcirclePreference.CROPCIRCLE_SENSOR_SPREAD
     * CropcirclePreference.SENSOR_TYPE
     * CropcirclePreference.SPECTRAL_CHANNELS
     * @return created SensorMetadata object
     */
    @Override
    protected SensorMetadata buildMetadataFromPreferences() {
        String fileFormat = CropcirclePreference.SENSOR_DATA_FILE_FORMAT.getValue();
        int sensorHeigth = Integer.parseInt(CropcirclePreference.CROPCIRCLE_SENSOR_HEIGTH.getValue());
        int numberOfSensors = Integer.parseInt(CropcirclePreference.NUMBER_OF_CROPCIRCLE_SENSORS.getValue());
        String cropCircleSensorSpread = CropcirclePreference.CROPCIRCLE_SENSOR_SPREAD.getValue();
        String sensorType = CropcirclePreference.SENSOR_TYPE.getValue();
        String spectralChannels = CropcirclePreference.SPECTRAL_CHANNELS.getValue();
        return new SensorMetadata(fileFormat, sensorHeigth, numberOfSensors, cropCircleSensorSpread, sensorType, spectralChannels);
    }
    
    private class EventProducer extends BaseGuiEventProducer {

        public EventProducer() {
            super(SensorEventKey.class);
        }
    }
}
